#!/usr/bin/perl
use FindBin;
use lib "$FindBin::Bin/lib";
use lib "$FindBin::Bin/lib/perl-lib/lib/perl5";

use strict;
use IO::File;
use Getopt::Long;
use File::Basename;
use AutoExecUtils;

# mydb1.__data_transfer_cache_size=0
# mydb2.__data_transfer_cache_size=0
# mydb3.__data_transfer_cache_size=0
# mydb2.__db_cache_size=281018368
# mydb1.__db_cache_size=281018368
# mydb3.__db_cache_size=331350016
# mydb1.__inmemory_ext_roarea=0
# mydb2.__inmemory_ext_roarea=0
# mydb3.__inmemory_ext_roarea=0
# mydb1.__inmemory_ext_rwarea=0
# mydb2.__inmemory_ext_rwarea=0
# mydb3.__inmemory_ext_rwarea=0
# mydb1.__java_pool_size=0
# mydb2.__java_pool_size=0
# mydb3.__java_pool_size=0
# mydb1.__large_pool_size=4194304
# mydb2.__large_pool_size=4194304
# mydb3.__large_pool_size=4194304
# mydb1.__oracle_base='/db/oracle/app/oracle'#ORACLE_BASE set from environment
# mydb3.__oracle_base='/db/oracle/app/oracle'#ORACLE_BASE set from environment
# mydb2.__oracle_base='/db/oracle/app/oracle'#ORACLE_BASE set from environment
# mydb1.__pga_aggregate_target=268435456
# mydb2.__pga_aggregate_target=268435456
# mydb3.__pga_aggregate_target=268435456
# mydb1.__sga_target=805306368
# mydb2.__sga_target=805306368
# mydb3.__sga_target=805306368
# mydb1.__shared_io_pool_size=29360128
# mydb2.__shared_io_pool_size=29360128
# mydb3.__shared_io_pool_size=25165824
# mydb2.__shared_pool_size=473956352
# mydb1.__shared_pool_size=473956352
# mydb3.__shared_pool_size=427819008
# mydb1.__streams_pool_size=0
# mydb2.__streams_pool_size=0
# mydb3.__streams_pool_size=0
# mydb1.__unified_pga_pool_size=0
# mydb2.__unified_pga_pool_size=0
# mydb3.__unified_pga_pool_size=0
# *.audit_file_dest='/db/oracle/app/oracle/admin/mydb_primary/adump'
# *.audit_trail='db'
# *.cluster_database=true
# *.compatible='19.0.0'
# *.control_files='+DATA/MYDB_PRIMARY/CONTROLFILE/current.261.1119292377','+ARCH/MYDB_PRIMARY/CONTROLFILE/current.256.1119292377'
# *.db_block_size=8192
# *.db_create_file_dest='+DATA'
# *.db_name='mydb'
# *.db_recovery_file_dest='+ARCH'
# *.db_recovery_file_dest_size=10g
# *.db_unique_name='mydb_primary'
# *.diagnostic_dest='/db/oracle/app/oracle'
# *.dispatchers='(PROTOCOL=TCP) (SERVICE=mydbXDB)'
# *.enable_pluggable_database=true
# family:dw_helper.instance_mode='read-only'
# mydb2.instance_number=2
# mydb1.instance_number=1
# *.local_listener='-oraagent-dummy-'
# *.log_archive_dest_1='LOCATION=+ARCH'
# *.log_archive_format='%t_%s_%r.dbf'
# *.nls_language='AMERICAN'
# *.nls_territory='AMERICA'
# *.open_cursors=300
# *.pga_aggregate_target=256m
# *.processes=300
# *.remote_login_passwordfile='exclusive'
# *.sga_target=768m
# mydb2.thread=2
# mydb1.thread=1
# mydb1.undo_tablespace='UNDOTBS1'
# mydb2.undo_tablespace='UNDOTBS2'

sub usage {
    my $pname = $FindBin::Script;

    print("$pname --pfileref <ORACLE_USER>  --dbinstances <DB Instaces> --DB_UNIQUE_NAME <DB UNIQUE NAME> --dirconvmap <Directory convert map>\n");
    exit(1);
}

sub convertPfileRef {
    my ( $pfileReference, $dbUniqName, $memPercent, $dbInstances, $dirConvMap ) = @_;

    my $pfileContent  = '';
    my $perDbConfMap  = {};
    my @pfileRefArray = split( /\n/, $pfileReference );
    my $refDbName;
    my $refDbUniqName;
    my $refServiceNames;
    foreach my $line (@pfileRefArray) {
        $line =~ s/^\s*|\s*$//g;
        if ( $line =~ /^#/ or $line =~ /_oracle_base\s*=/ ) {
            next;
        }

        if ( defined($dirConvMap) ) {
            $line = convertDir( $line, $dirConvMap );
        }

        if ( $memPercent != 100 ) {
            if (   $line =~ /_pool_size\s*=\s*(\d+)/
                or $line =~ /_cache_size\s*=\s*(\d+)/
                or $line =~ /_target\s*=\s*(\d+)/ )
            {
                my $val    = $1;
                my $newVal = $val;
                $newVal = int( $val * $memPercent / 100 );
                $line =~ s/$val/$newVal/;
            }
        }

        if ( $line =~ /^(\w+\d+)\./ ) {
            my $dbInsName = $1;
            my $insConf   = $perDbConfMap->{$dbInsName};
            if ( not defined($insConf) ) {
                $insConf = {};
                $perDbConfMap->{$dbInsName} = $insConf;
            }
            $insConf->{$line} = 1;
        }
        else {
            my @lineInfo = split( /\s*=\s*/, $line, 2 );
            if ( $lineInfo[0] =~ /\.db_name$/ ) {
                $refDbName = $lineInfo[1];
                $refDbName =~ s/^\s*['"]|['"]\s*$//g;
            }
            elsif ( $lineInfo[0] =~ /\.db_unique_name$/ ) {
                $refDbUniqName = $lineInfo[1];
                $refDbUniqName =~ s/^\s*['"]|['"]\s*$//g;
            }
            elsif ( $lineInfo[0] =~ /\.service_names$/ ) {
                $refServiceNames = $lineInfo[1];
                $refServiceNames = ~s/^\s*['"]|['"]\s*$//g;
            }
            $pfileContent = $pfileContent . $line . "\n";
        }
    }

    my @instancesConf   = ();
    my @oldDBInstances  = sort( keys(%$perDbConfMap) );
    my @destDbInstances = sort(@$dbInstances);
    my $insCount        = scalar(@destDbInstances);
    for ( my $i = 0 ; $i < $insCount ; $i++ ) {
        my $oldInsName = $oldDBInstances[$i];
        my $newInsName = $destDbInstances[$i];

        my $insConf = $perDbConfMap->{$oldInsName};
        foreach my $line ( keys(%$insConf) ) {
            $line =~ s/^$oldInsName/$newInsName/;
            push( @instancesConf, $line );
        }
    }
    $pfileContent = join( "\n", @instancesConf ) . "\n" . $pfileContent;

    if ( not defined($dbUniqName) ) {
        $dbUniqName = $refDbUniqName;
    }

    my $ucDbUniqName    = uc($dbUniqName);
    my $ucRefDbUniqName = uc($refDbUniqName);
    if ( defined($refDbUniqName) and $dbUniqName ne $refDbUniqName ) {
        #前面加上后向断言，后面加上前向断言，防止在一个单词内部出现db唯一名字串被替换
        $pfileContent =~ s/(?<!\w)${refDbUniqName}(?!\w)/${refDbUniqName}_FOR_REPL_/g;
        $pfileContent =~ s/(?<!\w)${ucRefDbUniqName}(?!\w)/${ucRefDbUniqName}_FOR_REPL_/g;

        $pfileContent =~ s/(?<!\w)${dbUniqName}(?!\w)/${refDbUniqName}/g;
        $pfileContent =~ s/(?<!\w)${ucDbUniqName}(?!\w)/${ucRefDbUniqName}/g;

        $pfileContent =~ s/(?<!\w)${refDbUniqName}_FOR_REPL_(?!\w)/${dbUniqName}/g;
        $pfileContent =~ s/(?<!\w)${ucRefDbUniqName}_FOR_REPL_(?!\w)/${ucDbUniqName}/g;

        $pfileContent =~ s/db_name='\w+'/db_name='$refDbName'/g;

        $pfileContent =~ s/log_archive_dest_state_(\d+)='\w'/log_archive_dest_state_$1='defer'/ig;
    }

    my $dbServiceNames;
    if ( defined($refServiceNames) or $refServiceNames eq '' ) {
        $dbServiceNames = "$refDbName,$dbUniqName";
    }
    else {
        my @oldServiceNames = split( /\s*,\s*/, $refServiceNames );
        my @newServiceNames = ( $refDbName, $dbUniqName );
        foreach my $serviceName (@oldServiceNames) {
            if ( $serviceName ne $refDbName and $serviceName ne $refDbUniqName ) {
                push( @newServiceNames, $serviceName );
            }
        }
        $dbServiceNames = join( ',', @newServiceNames );
    }
    $pfileContent = $pfileContent . "*.service_names='$dbServiceNames'\n";

    my $pfileInfo = {
        content        => $pfileContent,
        dbName         => $refDbName,
        dbUniqName     => $dbUniqName,
        dbServiceNames => $dbServiceNames
    };

    return $pfileInfo;
}

sub convertDir {
    my ( $dir, $dirConvMap ) = @_;
    while ( my ( $oldDir, $newDir ) = each(%$dirConvMap) ) {
        $dir =~ s/\Q$oldDir\E/$newDir/e;
    }

    return $dir;
}

sub getPfileConfDirs {
    my ( $dbUniqName, $pfileContent, $dirConvMap ) = @_;

    my $controlFiles    = '';
    my $controlFileDirs = '';
    my $logArchDestDir;
    my $otherDirs   = '';
    my $asmDirs     = '';
    my $fileSysDirs = '';
    my $dirConfMap  = {};

    foreach my $line ( split( /\n/, $pfileContent ) ) {
        if ( $line =~ /^\s*#/ ) {
            next;
        }
        $line =~ s/#.*$//;

        my @lineInfo = split( /\s*=\s*/, $line, 2 );
        if ( $lineInfo[0] =~ /\.control_files$/ ) {
            my $controlFilesConf = $lineInfo[1];
            $controlFilesConf =~ s/^\s*|\s*$//g;
            foreach my $controlFile ( split( /\s*,\s*/, $controlFilesConf ) ) {
                $controlFile =~ s/^\s*['"]|['"]\s*$//g;
                $controlFiles = $controlFiles . $controlFile . "\n";
                my $controlFileDir = dirname($controlFile);
                $controlFileDirs = $controlFileDirs . $controlFileDir . "\n";
                if ( $controlFileDir =~ /^\+/ ) {
                    $asmDirs = $asmDirs . $controlFileDir . "\n";
                }
                else {
                    $fileSysDirs = $fileSysDirs . $controlFileDir . "\n";
                }
            }
            $controlFiles    =~ s/\s*$//;
            $controlFileDirs =~ s/\s*$//;

            $dirConfMap->{ $lineInfo[0] } = $controlFiles;
        }
        elsif ( $lineInfo[0] =~ /\.log_archive_dest_\d+$/ ) {
            my $logArchDestDef = $lineInfo[1];
            if ( $logArchDestDef =~ /LOCATION=(.*?)\s+\w+=/ ) {
                $logArchDestDir = $1;
            }
            elsif ( $logArchDestDef =~ /LOCATION=(.*?)\s*'$/ ) {
                $logArchDestDir = $1;
            }
            $dirConfMap->{ $lineInfo[0] } = $logArchDestDir;

            if ( $logArchDestDir =~ /^\+/ ) {
                $asmDirs = $asmDirs . $logArchDestDir . "\n";
            }
            else {
                $fileSysDirs = $fileSysDirs . $logArchDestDir . "\n";
            }
        }
        elsif ( $lineInfo[1] =~ /^'\// or $lineInfo[1] =~ /^'\+\w+/ ) {
            my $dirConfVal = '';
            my $filesConf  = $lineInfo[1];
            $filesConf =~ s/^\s*|\s*$//g;
            foreach my $file ( split( /\s*,\s*/, $filesConf ) ) {
                $file =~ s/^\s*['"]|['"]\s*$//g;
                $dirConfVal = $dirConfVal . $file . "\n";

                if ( $file =~ /^\+/ ) {
                    $asmDirs = $asmDirs . $file . "\n";
                }
                else {
                    $fileSysDirs = $fileSysDirs . $file . "\n";
                }
            }
            $otherDirs = $otherDirs . $dirConfVal;
            $dirConfVal =~ s/\s*$//;
            $dirConfMap->{ $lineInfo[0] } = $dirConfVal;
        }
    }
    $otherDirs   =~ s/\s*$//;
    $asmDirs     =~ s/\s*$//;
    $fileSysDirs =~ s/\s*$//;

    my $pfileDirsInfo = {
        controlFiles    => $controlFiles,
        controlFileDirs => $controlFileDirs,
        logArchDestDir  => $logArchDestDir,
        otherDirs       => $otherDirs,
        asmDirs         => $asmDirs,
        fileSysDirs     => $fileSysDirs,
        dirConfMap      => $dirConfMap
    };

    return $pfileDirsInfo;
}

sub main {
    my $opts = {};
    GetOptions(
        $opts, qw{
            pfileref=s
            DB_UNIQUE_NAME=s
            mempercent=s
            dbinstances=s
            dirconvmap=s
        }
    );

    my $hasOptErr      = 0;
    my $pfileReference = $opts->{pfileref};
    my $dbUniqName     = $opts->{DB_UNIQUE_NAME};    #生成目标DB的唯一名
    my $dirConvMapTxt  = $opts->{dirconvmap};
    my $dbInstancesTxt = $opts->{dbinstances};       #生成目标的实例名列表
    my $memPercent     = $opts->{mempercent};        #生成目标的实例的内存参数跟模版DB的百分比比例

    if ( not defined($dbUniqName) or $dbUniqName eq '' ) {
        $hasOptErr = 1;
        print("ERROR: Must defined DB_UNIQUE_NAME by option --DB_UNIQUE_NAME.\n");
    }

    if ( not defined($dbInstancesTxt) or $dbInstancesTxt eq '' ) {
        $hasOptErr = 1;
        print("ERROR: Must defined DB instances by option --dbinstances.\n");
    }

    if ( not defined($pfileReference) or $pfileReference eq '' ) {
        $hasOptErr = 1;
        print("ERROR: Must defined pfile reference by option --pfileref.\n");
    }
    if ( $hasOptErr == 1 ) {
        usage();
    }

    my $hasError = 0;

    if ( not defined($memPercent) ) {
        $memPercent = 100;
    }
    else {
        $memPercent = int($memPercent);
    }

    $dbInstancesTxt =~ s/^\s*|\s*$//g;
    $dbInstancesTxt =~ s/\\n/\n/g;
    my @dbInstances = ();
    foreach my $insName ( split( /\n/, $dbInstancesTxt ) ) {
        $insName =~ s/^\s*//;
        $insName =~ s/\s.*$//;
        push( @dbInstances, $insName );
    }

    $pfileReference =~ s/^\s*|\s*$//g;
    $pfileReference =~ s/\\n/\n/g;

    my $dirConvMap = {};
    if ( defined($dirConvMapTxt) and $dirConvMapTxt ne '' ) {
        $dirConvMapTxt =~ s/^\s*|\s*$//g;
        $dirConvMapTxt =~ s/\\n/\n/g;
        foreach my $line ( split( /\n/, $dirConvMapTxt ) ) {
            $line =~ s/^\s*|\s*$//g;
            my @dirMap = split( /\s*,\s*/, $line );
            if ( scalar(@dirMap) == 2 ) {
                $dirConvMap->{ $dirMap[0] } = $dirMap[1];
            }
            else {
                print("ERROR: Malform directory mapping config line:$line, Example: /xx/yy,/xx1/yy1");
                $hasError = 1;
            }
        }
    }

    my $pfileInfo     = convertPfileRef( $pfileReference, $dbUniqName, $memPercent, \@dbInstances, $dirConvMap );
    my $pfileDirsInfo = getPfileConfDirs( $dbUniqName, $pfileInfo->{content}, $dirConvMap );

    my $out = {
        pfile           => $pfileInfo->{content},
        dbName          => $pfileInfo->{dbName},
        controlFiles    => $pfileDirsInfo->{controlFiles},
        controlFileDirs => $pfileDirsInfo->{controlFileDirs},
        logArchDestDir  => $pfileDirsInfo->{logArchDestDir},
        otherDirs       => $pfileDirsInfo->{otherDirs},
        asmDirs         => $pfileDirsInfo->{asmDirs},
        fileSysDirs     => $pfileDirsInfo->{fileSysDirs},
        dirConfMap      => $pfileDirsInfo->{dirConfMap}
    };

    AutoExecUtils::saveOutput($out);

    if ( $hasError == 0 ) {
        print("FINE: Generate dumplacted db pfile success.\n");
    }

    return $hasError;
}

exit( main() );
